package com.wl.common.security.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.wl.common.core.constant.SecurityConstants;
import com.wl.common.security.bean.JwtProperties;
import com.wl.common.security.register.LoginUserRegistry;
import com.wl.system.api.model.LoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: wanglin
 * @date: 2023-09-12 周二
 * @Version: 1.0
 * @Description: JwtToken生成的工具类
 * JWT token的格式：header.payload.signature
 * header的格式（算法、token的类型）：
 * {"alg": "HS512","typ": "JWT"}
 * payload的格式（用户名、创建时间、生成时间）：
 * {"sub":"wang","created":1489079981393,"exp":1489684781}
 * signature的生成算法：
 * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),JwtParamsConfig.secret)
 */
public class JwtTokenUtil {
    private final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);

    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private LoginUserRegistry loginUserRegistry;
    protected final long MILLIS_SECOND = 1000L;
    protected final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    /**
     * 获取请求token
     */
    public String getToken() {
        return getToken(ServletUtils.getRequest());
    }

    /**
     * 根据request获取请求token
     */
    public String getToken(HttpServletRequest request) {
        // 从header获取token标识
        String token = request.getHeader(jwtProperties.getTokenHeader());
        return StrUtil.isNotBlank(token) ? replaceTokenPrefix(token) : null;
    }

    /**
     * 裁剪token前缀,判断token是否是以 'Bearer ' 开头
     */
    public String replaceTokenPrefix(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(jwtProperties.getTokenHead())) {
            token = token.replaceFirst(jwtProperties.getTokenHead(), "");
        }
        return token;
    }

    /**
     * 根据负责生成JWT的token
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
                .compact();
    }

    /**
     * 根据用户信息生成token
     */
    public String generateToken(LoginUser loginUser) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(SecurityConstants.USER_KEY, loginUser.getToken());
        claims.put(SecurityConstants.DETAILS_USERNAME, loginUser.getUsername());
        return generateToken(claims);
    }

    /**
     * 生成token的过期时间
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * MILLIS_MINUTE);
    }

    /**
     * 从token中获取登录用户名
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = parseToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 验证token是否还有效
     *
     * @param token     客户端传入的token
     * @param loginUser 从数据库中查询出来的用户信息
     */
    public boolean validateToken(String token, LoginUser loginUser) {
        String username = getUserName(token);
        return username.equals(loginUser.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 验证token是否还有效
     *
     * @param token    客户端传入的token
     * @param username 从数据库中查询出来的用户信息
     */
    public boolean validateToken(String token, String username) {
        String name = getUserName(token);
        return username.equals(name) && !isTokenExpired(token);
    }

    /**
     * 判断token是否已经失效
     */
    public boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return ObjUtil.isNotNull(expiredDate) ? expiredDate.before(new Date()) : true;
    }

    /**
     * 从token中获取过期时间
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = parseToken(token);
        return ObjUtil.isNotNull(claims) ? claims.getExpiration() : null;
    }

    /**
     * 判断token在指定时间内是否刚刚刷新过
     *
     * @param token 原token
     * @param time  指定时间（秒）
     */
    private boolean tokenRefreshJustBefore(String token, int time) {
        Claims claims = parseToken(token);
        Date created = claims.get(SecurityConstants.CLAIM_KEY_CREATED, Date.class);
        Date refreshDate = new Date();
        //刷新时间在创建时间的指定时间内
        if (refreshDate.after(created) && refreshDate.before(DateUtil.offsetSecond(created, time))) {
            return true;
        }
        return false;
    }

    /**
     * 判断token在指定时间内是否刚刚刷新过
     *
     * @param claims 原claims
     * @param time   指定时间（秒）
     */
    private boolean tokenRefreshJustBefore(Claims claims, int time) {
        Date created = claims.get(SecurityConstants.CLAIM_KEY_CREATED, Date.class);
        Date refreshDate = new Date();
        //刷新时间在创建时间的指定时间内
        if (refreshDate.after(created) && refreshDate.before(DateUtil.offsetSecond(created, time))) {
            return true;
        }
        return false;
    }

    /**
     * 从令牌中获取数据声明，从token中获取JWT中的负载
     *
     * @param token 令牌
     * @return 数据声明
     */
    public Claims parseToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            LOGGER.error("Token已过期：{}", token);
        } catch (Exception e) {
            LOGGER.error("Token解析失败:{}", token);
        }
        return claims;
    }

    /**
     * 从token中获取JWT中的负载
     *
     * @param token
     * @return
     */
    public Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(jwtProperties.getSecret())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.error("Token解析失败:{}", token);
        }
        return claims;
    }

    /**
     * 根据令牌获取用户标识
     *
     * @param token 令牌
     * @return 用户ID
     */
    public String getUserKey(String token) {
        Claims claims = parseToken(token);
        return getValue(claims, SecurityConstants.USER_KEY);
    }

    /**
     * 根据令牌获取用户标识
     *
     * @param claims 身份信息
     * @return 用户ID*-+
     */
    public String getUserKey(Claims claims) {
        return getValue(claims, SecurityConstants.USER_KEY);
    }

    /**
     * 根据令牌获取用户ID
     *
     * @param token 令牌
     * @return 用户ID
     */
    public String getUserId(String token) {
        Claims claims = parseToken(token);
        return getValue(claims, SecurityConstants.DETAILS_USER_ID);
    }

    /**
     * 根据身份信息获取用户ID
     *
     * @param claims 身份信息
     * @return 用户ID
     */
    public String getUserId(Claims claims) {
        return getValue(claims, SecurityConstants.DETAILS_USER_ID);
    }

    /**
     * 根据身份信息获取用户名
     *
     * @param claims 身份信息
     * @return 用户名
     */
    public String getUserName(Claims claims) {
        return getValue(claims, SecurityConstants.DETAILS_USERNAME);
    }

    /**
     * 从token中获取登录用户名
     */
    public String getUserName() {
        String token = getToken();
        return StrUtil.isNotBlank(token) ? this.getUserName(token) : null;
    }

    /**
     * 根据令牌获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUserName(String token) {
        Claims claims = parseToken(token);
        return getValue(claims, SecurityConstants.DETAILS_USERNAME);
    }

    /**
     * 根据身份信息获取键值
     *
     * @param claims 身份信息
     * @param key    键
     * @return 值
     */
    public String getValue(Claims claims, String key) {
        if (ObjUtil.isNotNull(claims)) {
            return Convert.toStr(claims.get(key), "");
        }
        return null;
    }

    /**
     * 验证令牌有效期，相差不足10分钟，自动刷新缓存
     *
     * @param loginUser
     */
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= jwtProperties.getRefreshTime() * MILLIS_MINUTE) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + jwtProperties.getExpiration() * MILLIS_MINUTE);
        // 根据username将loginUser缓存
        loginUserRegistry.registerNewLoginUser(loginUser);
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser() {
        String userName = getUserName();
        return StrUtil.isNotBlank(userName) ? getLoginUser(userName) : null;
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUserByToken(String token) {
        String userName = getUserName(token);
        return StrUtil.isNotBlank(userName) ? getLoginUser(userName) : null;
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(String username) {
        return loginUserRegistry.getLoginUser(username);
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser) {
        if (ObjUtil.isNotNull(loginUser) && StrUtil.isNotEmpty(loginUser.getToken())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户缓存信息
     */
    public void delLoginUser() {
        String username = getUserName();
        if (StrUtil.isNotEmpty(username)) {
            loginUserRegistry.removeLoginUser(username);
        }
    }

    /**
     * 删除用户缓存信息
     */
    public void delLoginUser(String username) {
        if (StrUtil.isNotEmpty(username)) {
            loginUserRegistry.removeLoginUser(username);
        }
    }

    /**
     * 删除用户缓存信息
     */
    public void delLoginUserByToken(String token) {
        if (StrUtil.isNotEmpty(token)) {
            String userName = getUserName(token);
            loginUserRegistry.removeLoginUser(userName);
        }
    }

    /**
     * 删除用户缓存信息
     */
    public void delLoginUser(LoginUser loginUser) {
        if (ObjUtil.isNotNull(loginUser)) {
            loginUserRegistry.removeLoginUser(loginUser.getUsername());
        }
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public boolean isSuperAdmin() {
        LoginUser loginUser = getLoginUser();
        return ObjUtil.isNotNull(loginUser) ? loginUser.getRoles().stream().anyMatch(e -> SecurityConstants.SUPER_ADMIN.equals(e)) : false;
    }
}
